﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.SceneManagement;
using System.Text.RegularExpressions;
using UnityEditor;
using System.IO;
using System.Text;
using System.Xml;
using System;

namespace Nirvana
{
	/// <summary>
	/// 场景数据配置脚本
	/// </summary>
	[RequireComponent(typeof(SceneGridView))]
	[ExecuteInEditMode]
	public partial class SceneLogic : MonoBehaviour
	{
#if UNITY_EDITOR
		[SerializeField]
		[Tooltip("The ID of this scene.")]
		private int sceneID;

		[SerializeField]
		[Tooltip("The name of this scene.")]
		private string sceneName;

		[SerializeField]
		[Tooltip("The level limit to enter this level.")]
		private int levelLimit;

		[SerializeField]
		[Tooltip("领域税收.")]
		private bool isTax;

		[SerializeField]
		[Tooltip("是否禁止PK")]
		private bool isForbidPK;

		[SerializeField]
		[Tooltip("The special logic type.")]
		[EnumLabel("Special Logic Type")]
		private SpecialLogicType specialLogicType;

		[SerializeField]
		[Tooltip("场景超时时间")]
		private int sceneTimeout;

		[SerializeField]
		[Tooltip("是否超时剔除场景")]
		private bool isTimeoutKick;

		[SerializeField]
		[Tooltip("场景出生点ID.")]
		private int townSceneID;

		[SerializeField]
		[Tooltip("场景出生点X.")]
		private int townSceneX;

		[SerializeField]
		[Tooltip("场景出生点Y.")]
		private int townSceneY;

		[SerializeField]
		[Tooltip("场景出生朝向.")]
		private int townSceneRotation;

		[SerializeField]
		[Tooltip("显示天气效果")]
		private bool showWeather = false;

		public int SceneID { get { return this.sceneID; } }

		/// <summary>
		/// 记录地图内详细有什么东西：怪物、NPC、传送点等
		/// </summary>
		/// <param name="path">文件保存路径</param>
		/// <param name="compress">地图cell数据是否压缩</param>
		/// <returns></returns>
		public bool SaveSceneLua(string path,bool compress)
        {
			var gridView = this.GetComponent<SceneGridView>();
			if(gridView == null)
            {
				Debug.LogError("Can not find the SceneGridView Component.");
				return false;
            }
			// 获取当前场景的名字和assetbundle.
			var scene = EditorSceneManager.GetActiveScene();
			if(scene == null || scene.name == string.Empty || scene.name == "main")
            {
				return false;
            }

			Regex reg = new Regex("_detail |_logic|_main", RegexOptions.IgnoreCase);
			var bundlename = AssetImporter.GetAtPath(scene.path).assetBundleName;
            if (string.IsNullOrEmpty(bundlename))
            {
				EditorUtility.DisplayDialog("生成失败", "AssetBundleName没有设置", "确定");
				return false;
			}

			bundlename = reg.Replace(bundlename, "");
			var assetname = Path.GetFileNameWithoutExtension(scene.path);
			if (string.IsNullOrEmpty(assetname))
			{
				EditorUtility.DisplayDialog("生成失败", "invalid assetName", "确定");
				return false;
			}

			assetname = reg.Replace(assetname, "");

			string format =
@"return {{
	id = {0},
	name = ""{1}"",
	scene_type = {2},
	bundle_name = ""{3}"",
	asset_name = ""{4}"",
	width = {5},
	height = {6},
	origin_x = {7},
	origin_y = {8},
	levellimit = {9},
    show_weather = {10},
	npcs = {{{11}
	}},
	monsters = {{{12}
	}},
	doors = {{{13}
	}},
    gathers = {{{14}
	}},
    fences = {{{15}
	}},
	mask = ""{16}"",
    jumppoints = {{{17}
    }},
    dovepoints = {{{18}
    }},
    is_forbid_pk = ""{19}"",
    effects = {{{20}
    }},
}}";
			var npcs = this.GetSceneNPCLua();
			var monsters = this.GetSceneMonsterLua();
			var doors = this.GetSceneDoorLua();
			var gathers = this.GetSceneGatherLua();
			var fences = this.GetSceneFencesLua();
			var mask = this.GetMapEncodedMask(gridView, compress);
			var jumppoint = this.GetSceneJumppointLua();
			var dovepoint = this.GetSceneDovepointLua();
			var effects = this.GetSceneEffectLua();
			var sourceText = string.Format(
				format,
				this.sceneID,
				this.sceneName,
				(int)this.specialLogicType,
				bundlename,
				assetname,
				gridView.Row,
				gridView.Column,
				this.transform.position.x,
				this.transform.position.z,
				this.levelLimit,
				this.showWeather ? "1" : "0",
				npcs,
				monsters,
				doors,
				gathers,
				fences,
				mask,
				jumppoint,
				dovepoint,
				this.isForbidPK ? "1" : "0",
				effects);

			var fileStream = new FileStream(path, FileMode.Create);
			var writer = new StreamWriter(fileStream);
			writer.Write(sourceText);
			writer.Close();
			fileStream.Close();
			return true;

		}

		/// <summary>
		/// 生成服务端使用文件：记录地图内详细有什么东西：怪物、NPC、传送点等
		/// </summary>
		/// <param name="path"></param>
		public void SaveSceneXML(string path)
		{
			var setting = new XmlWriterSettings();

			setting.Indent = true;
			setting.IndentChars = "\t";
			setting.NewLineChars = "\n";
			setting.NewLineHandling = NewLineHandling.Replace;

			using (var writer = XmlWriter.Create(path, setting))
			{
				writer.WriteStartDocument();
				writer.WriteStartElement("scene");

				writer.WriteElementString("id", this.sceneID.ToString());
				writer.WriteElementString("name", this.sceneName);
				writer.WriteElementString("mapid", this.sceneID.ToString());

				writer.WriteElementString("levellimit", this.levelLimit.ToString());
				writer.WriteElementString("istax", this.isTax ? "1" : "0");
				writer.WriteElementString("is_forbid_pk", this.isForbidPK ? "1" : "0");
				writer.WriteElementString("speciallogic_type", ((int)this.specialLogicType).ToString());
				writer.WriteElementString("scene_timeout", this.sceneTimeout.ToString());
				writer.WriteElementString("is_timeout_kick", this.isTimeoutKick ? "1" : "0");

				writer.WriteStartElement("townpoint");
				writer.WriteElementString("sceneid", this.townSceneID.ToString());
				writer.WriteElementString("scenex", this.townSceneX.ToString());
				writer.WriteElementString("sceney", this.townSceneY.ToString());
				writer.WriteElementString("scenerotation_y", this.townSceneRotation.ToString());

				writer.WriteEndElement();

				writer.WriteStartElement("triggerareas");
				writer.WriteEndElement();

				writer.WriteStartElement("triggers");
				writer.WriteEndElement();

				this.SaveNPCs(writer);
				this.SaveMonsterPoints(writer);
				this.SaveDoorsPoints(writer);
				this.SaveGetherPoints(writer);

				writer.WriteEndElement();
				writer.WriteEndDocument();
			}
		}

		/// <summary>
		/// 生成服务端使用文件：记录地图中，总大小、可走区域、不可走区域等
		/// </summary>
		/// <param name="path"></param>
		public void SaveMapXML(string path)
		{
			var gridView = this.GetComponent<SceneGridView>();
			if (gridView == null)
			{
				Debug.LogError("Can not find the SceneGridView component.");
				return;
			}

			var setting = new XmlWriterSettings();

			setting.Indent = true;
			setting.IndentChars = "\t";
			setting.NewLineChars = "\n";
			setting.NewLineHandling = NewLineHandling.Replace;

			using (var writer = XmlWriter.Create(path, setting))
			{
				writer.WriteStartDocument();
				writer.WriteStartElement("scene");

				writer.WriteElementString("id", this.sceneID.ToString());
				writer.WriteElementString("width", gridView.Row.ToString());
				writer.WriteElementString("height", gridView.Column.ToString());
				writer.WriteElementString("mask", this.GetMapMaskShrinked(gridView));

				writer.WriteEndElement();
				writer.WriteEndDocument();
			}
		}

		private string GetSceneNPCLua()
        {
			var sb = new StringBuilder();
			var npcs = this.GetComponentsInChildren<SceneNPC>();
			foreach(var npc in npcs)
            {
				int x, y;
				this.TransformWorldToLogic(npc.transform.position, out x, out y);
				float rotationY = npc.transform.eulerAngles.y;
				string paths = "";
                if (npc.IsWalking)
                {
					foreach (var path in npc.Paths) paths += string.Format("{{x={0}, y={1}}},",path.x,path.y);
					paths = string.Format("{{{0}}}", paths);
				}
				else
				{
					paths = "{}";
				}
				var npcLua = string.Format("\n\t\t{{id={0}, x={1}, y={2},rotation_y = {3},is_walking = {4},paths = {5}}},",
					npc.ID, x, y, rotationY, npc.IsWalking ? 1 : 0, paths);

				sb.Append(npcLua);
            }
			return sb.ToString();
        }

		private string GetSceneMonsterLua()
        {
			var sb = new StringBuilder();
			var monsters = this.GetComponentsInChildren<SceneMonster>();
			foreach(var monster in monsters)
            {
				int x, y;
				this.TransformWorldToLogic(monster.transform.position, out x, out y);

				var monsterLua = string.Format("\n\t\t{{id={0}, x={1}, y={2}, mapicon={3}}},", monster.ID, x, y, monster.IsShowMapIcon ? "1" : "0");
				sb.Append(monsterLua);

			}
			return sb.ToString();

		}

		private string GetSceneDoorLua()
        {
			var sb = new StringBuilder();
			var doors = this.GetComponentsInChildren<SceneDoor>();
			foreach (var door in doors)
			{
				int x;
				int y;
				this.TransformWorldToLogic(door.transform.position, out x, out y);
				var doorLua = string.Format(
					"\n\t\t{{id={0}, type={1}, level={2}, target_scene_id={3}, target_door_id={4}, offset={{{5}, {6}, {7}}}, rotation={{{8}, {9}, {10}}}, x={11}, y={12}, res_id = {13},roleoffset={{{14}, {15}, {16}}}, roleotation_y={17}}},",
					door.ID,
					door.DoorType,
					door.LimitLevel,
					door.TargetSceneID,
					door.TargetDoorID,
					door.Offset.x,
					door.Offset.y,
					door.Offset.z,
					door.Rotation.x,
					door.Rotation.y,
					door.Rotation.z,
					x,
					y,
					door.RoleOffset.x,
					door.RoleOffset.y,
					door.RoleOffset.z,
					door.RoleRotation,
					string.IsNullOrEmpty(door.ResID) ? "4032" : door.ResID);
				sb.Append(doorLua);
			}
			return sb.ToString();

		}

		private string GetSceneJumppointLua()
        {
			var builder = new StringBuilder();
			var elements = this.GetComponentsInChildren<SceneJumppoint>();
			foreach (var element in elements)
			{
				int x;
				int y;
				this.TransformWorldToLogic(element.transform.position, out x, out y);

				string cgs = "";
				if (element.PlayCG)
				{
					foreach (var cg in element.CGs)
					{
						cgs += string.Format("{{prof={0},bundle_name=\"{1}\",asset_name=\"{2}\",position={{x={3},y={4}}},rotation={5}}},", cg.prof, cg.cgController.BundleName,
							cg.cgController.AssetName, cg.position.x, cg.position.y, cg.rotationY);
					}
					cgs = string.Format("{{{0}}}", cgs);
				}
				else
				{
					cgs = "{}";
				}

				var content = string.Format(
					"\n\t\t{{id={0}, target_id={1}, range={2}, x={3}, y={4}, res_id=\"{5}\", jump_style={6}, jump_speed={7}, jump_ease=\"{8}\", play_cg={9},cgs={10}}},",
					element.ID,
					element.TargetID,
					element.Range,
					x,
					y,
					element.ResID,
					element.JumpStyle,
					element.JumpSpeed,
					element.JumpEase.ToString(),
					element.PlayCG ? 1 : 0,
					cgs
					);

				builder.Append(content);
			}

			return builder.ToString();
		}

		private string GetSceneDovepointLua()
		{
			var doveBuilder = new StringBuilder();
			var dovers = this.GetComponentsInChildren<SceneDovePoint>();
			foreach (var dove in dovers)
			{
				int x;
				int y;
				this.TransformWorldToLogic(dove.transform.position, out x, out y);
				var content = string.Format(
					"\n\t\t{{id={0}, x={1}, y={2},rotation={{{3}, {4}, {5}}}}},",
					dove.ID,
					x,
					y,
					dove.transform.rotation.x,
					dove.transform.rotation.y,
					dove.transform.rotation.z);
				doveBuilder.Append(content);
			}
			return doveBuilder.ToString();
		}

		private string GetSceneEffectLua()
		{
			var builder = new StringBuilder();
			var effects = this.GetComponentsInChildren<SceneEffect>();
			foreach (var effect in effects)
			{
				int x;
				int y;
				this.TransformWorldToLogic(effect.transform.position, out x, out y);

				var effectLua = string.Format(
					"\n\t\t{{bundle=\"{0}\", asset=\"{1}\", offset={{{2}, {3}, {4}}}, rotation={{{5}, {6}, {7}}}, x={8}, y={9}, scale={{{10}, {11}, {12}}}}},",
					effect.AssetID.BundleName,
					effect.AssetID.AssetName,
					effect.Offset.x,
					effect.Offset.y,
					effect.Offset.z,
					effect.Rotation.x,
					effect.Rotation.y,
					effect.Rotation.z,
					x,
					y,
					effect.transform.localScale.x,
					effect.transform.localScale.y,
					effect.transform.localScale.z);
				builder.Append(effectLua);
			}

			return builder.ToString();
		}

		private string GetSceneGatherLua()
		{
			var gatherBuilder = new StringBuilder();
			var gathers = this.GetComponentsInChildren<SceneGatherPoint>();
			foreach (var gather in gathers)
			{
				int x;
				int y;
				this.TransformWorldToLogic(gather.transform.position, out x, out y);

				var doorLua = string.Format(
					"\n\t\t{{id={0}, x={1}, y={2}}},",
					gather.ID,
					x,
					y);
				gatherBuilder.Append(doorLua);
			}

			return gatherBuilder.ToString();
		}

		private string GetSceneFencesLua()
		{
			var gatherBuilder = new StringBuilder();
			var fences = this.GetComponentsInChildren<SceneFence>();
			foreach (var fence in fences)
			{
				int x;
				int y;
				this.TransformWorldToLogic(fence.transform.position, out x, out y);

				var doorLua = string.Format(
					"\n\t\t{{id={0}, offset={{{1}, {2}, {3}}}, rotation={{{4}, {5}, {6}}}, x={7}, y={8},res_id=\"{9}\"}},",
					fence.ID,
					fence.Offset.x,
					fence.Offset.y,
					fence.Offset.z,
					fence.Rotation.x,
					fence.Rotation.y,
					fence.Rotation.z,
					x,
					y,
					fence.ResID);
				gatherBuilder.Append(doorLua);
			}

			return gatherBuilder.ToString();
		}


		private string GetMapEncodedMask(SceneGridView gridView,bool compress)
        {
            if (compress)
            {
				var dataStream = new MemoryStream(gridView.Column * gridView.Row);
				for(int j = 0; j < gridView.Column; ++j)
                {
					for(int i = 0; i < gridView.Row; ++i)
                    {
						var cell = gridView.GetCell(i, j) as SceneCell;
						dataStream.WriteByte((byte)cell.Ground);
                    }
                }

				var compressStream = new MemoryStream();
				var encoder = new SevenZip.Compression.LZMA.Encoder();
				encoder.SetCoderProperties(
					new SevenZip.CoderPropID[] { SevenZip.CoderPropID.EndMarker },
					new object[] { true });
				dataStream.Position = 0;
				encoder.WriteCoderProperties(compressStream);
				encoder.Code(dataStream, compressStream, -1, -1, null);
				compressStream.Flush();

				var bytes = new byte[compressStream.Length];
				compressStream.Seek(0, SeekOrigin.Begin);
				compressStream.Read(bytes, 0, bytes.Length);
				var mask = Convert.ToBase64String(bytes);
				return mask;

            }
            else
            {
				var maskBuilder = new StringBuilder();
				for (int j = 0; j < gridView.Column; ++j)
				{
					for (int i = 0; i < gridView.Row; ++i)
					{
						var cell = gridView.GetCell(i, j) as SceneCell;
						maskBuilder.Append((int)cell.Ground);
					}
				}

				return maskBuilder.ToString();
			}
        }
		private string GetMapMaskShrinked(SceneGridView gridView)
        {
			// 先拷贝一份int数组的Mask出来.
			var cells = new int[gridView.Column * gridView.Row];
			for (int j = 0; j < gridView.Column; ++j)
			{
				for (int i = 0; i < gridView.Row; ++i)
				{
					var cell = gridView.GetCell(i, j) as SceneCell;
					cells[(i * gridView.Column) + j] = (int)cell.Ground;
				}
			}

			if (gridView.Column > 2 && gridView.Row > 2)
            {
				// 逐列缩小
				for (int j = 1; j < gridView.Column - 1; ++j)
				{
					for (int i = 1; i < gridView.Row - 1; ++i)
					{
						var index = (i * gridView.Column) + j;
						var cell = cells[index];
						if (cell == (int)GroundType.Block || cell == (int)GroundType.ObstacleWay)
						{
							var cellNext = ((SceneCell)gridView.GetCell(i, j + 1)).Ground;
							var cellPrev = ((SceneCell)gridView.GetCell(i, j - 1)).Ground;
							if (cellNext != GroundType.Block && cellNext != GroundType.ObstacleWay)
							{
								cells[index] = (int)cellNext;
							}
							else if (cellPrev != GroundType.Block && cellPrev != GroundType.ObstacleWay)
							{
								cells[index] = (int)cellPrev;
							}
						}
					}
				}
				// 逐行缩小
				for (int i = 1; i < gridView.Row - 1; ++i)
				{
					for (int j = 1; j < gridView.Column - 1; ++j)
					{
						var index = (i * gridView.Column) + j;
						var cell = cells[index];
						if (cell == (int)GroundType.Block || cell == (int)GroundType.ObstacleWay)
						{
							var cellNext = ((SceneCell)gridView.GetCell(i + 1, j)).Ground;
							var cellPrev = ((SceneCell)gridView.GetCell(i - 1, j)).Ground;
							if (cellNext != GroundType.Block && cellNext != GroundType.ObstacleWay)
							{
								cells[index] = (int)cellNext;
							}
							else if (cellPrev != GroundType.Block && cellPrev != GroundType.ObstacleWay)
							{
								cells[index] = (int)cellPrev;
							}
						}
					}
				}

			}

			// 写入缩小后的Mask.
			var maskBuilder = new StringBuilder();
			for (int j = 0; j < gridView.Column; ++j)
			{
				for (int i = 0; i < gridView.Row; ++i)
				{
					int mask = cells[(i * gridView.Column) + j];
					maskBuilder.Append(mask);
				}
			}

			return maskBuilder.ToString();
		}

		private void SaveNPCs(XmlWriter writer)
		{
			writer.WriteStartElement("npcs");

			var npcs = this.GetComponentsInChildren<SceneNPC>();
			foreach (var npc in npcs)
			{
				if (npc.IsWalking)
					continue;
				writer.WriteStartElement("npc");
				writer.WriteElementString("id", npc.ID.ToString());
				this.WritePosition(writer, npc.transform.position);
				writer.WriteEndElement();
			}

			writer.WriteEndElement();
		}

		private void SaveMonsterPoints(XmlWriter writer)
		{
			writer.WriteStartElement("monsterpoints");

			var monsterPoints = this.GetComponentsInChildren<SceneMonster>();
			foreach (var monsterPoint in monsterPoints)
			{
				writer.WriteStartElement("point");
				writer.WriteElementString("monsterid", monsterPoint.ID.ToString());
				writer.WriteElementString("interval", monsterPoint.Interval.ToString());
				writer.WriteElementString("num", monsterPoint.Number.ToString());
				writer.WriteElementString("histroytotalnum", monsterPoint.TotalNum.ToString());
				this.WritePosition(writer, monsterPoint.transform.position);
				writer.WriteEndElement();
			}

			writer.WriteEndElement();
		}

		private void SaveDoorsPoints(XmlWriter writer)
		{
			writer.WriteStartElement("doors");

			var doors = this.GetComponentsInChildren<SceneDoor>();
			foreach (var door in doors)
			{
				writer.WriteStartElement("door");
				writer.WriteElementString("res_id", door.ResID);
				writer.WriteElementString("id", door.ID.ToString());
				writer.WriteElementString("type", door.DoorType.ToString());
				writer.WriteElementString("level", door.LimitLevel.ToString());
				writer.WriteElementString("target_scene_id", door.TargetSceneID.ToString());
				writer.WriteElementString("target_door_id", door.TargetDoorID.ToString());
				this.WritePosition(writer, door.transform.position + door.RoleOffset);
				writer.WriteElementString("rotation_y", door.RoleRotation.ToString());

				writer.WriteEndElement();
			}

			writer.WriteEndElement();
		}
		private void SaveGetherPoints(XmlWriter writer)
        {
			writer.WriteStartElement("gatherpoints");
			var gatherPoints = this.GetComponentsInChildren<SceneGatherPoint>();
			foreach(var gatherPoint in gatherPoints)
            {
				writer.WriteStartElement("gather");
				writer.WriteElementString("id", gatherPoint.ID.ToString());
				writer.WriteElementString("index", gatherPoint.Index.ToString());
				writer.WriteElementString("create_interval", gatherPoint.Interval.ToString());
				writer.WriteElementString("gather_time", gatherPoint.GatherTime.ToString());
				writer.WriteElementString("evil_add", gatherPoint.EvilAdd.ToString());
				writer.WriteElementString("disappear_after_gather", gatherPoint.DisappearAfterGather.ToString());
				this.WritePosition(writer, gatherPoint.transform.position);
				writer.WriteEndElement();

			}
			writer.WriteEndElement();
		}


		private void TransformWorldToLogic(Vector3 position, out int x,out int y)
        {
			var pos = this.transform.position;
			x = Mathf.FloorToInt((position.x - pos.x) * 2);
			y = Mathf.FloorToInt((position.z - pos.z) * 2);

		}

		private void WritePosition(XmlWriter writer, Vector3 position)
		{
			int x;
			int y;
			this.TransformWorldToLogic(position, out x, out y);
			writer.WriteElementString("x", x.ToString());
			writer.WriteElementString("y", y.ToString());
		}

		private void Update()
		{
			// Snap to grid.
			const float GridSize = 0.5f;
			const float GridSizeInverse = 1.0f / GridSize;

			var x = Mathf.Floor(transform.position.x * GridSizeInverse) / GridSizeInverse;
			var y = transform.position.y;
			var z = Mathf.Floor(transform.position.z * GridSizeInverse) / GridSizeInverse;

			this.transform.position = new Vector3(x, y, z);
		}

#endif

	}

}
